package org.example.judge.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.example.common.core.constants.Constants;
import org.example.common.core.constants.JudgeConstants;
import org.example.common.core.enums.CodeRunStatus;
import org.example.judge.callback.DockerStartResultCallback;
import org.example.judge.callback.StatisticsCallback;
import org.example.judge.config.DockerSandBoxPool;
import org.example.judge.domain.CompileResult;
import org.example.judge.domain.SandBoxExecuteResult;
import org.example.judge.service.SandboxPoolService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;


import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SandboxPoolServiceImpl implements SandboxPoolService {
    @Autowired
    private DockerSandBoxPool sandBoxPool;

    @Autowired
    private DockerClient dockerClient;

    private String containerId;

    private String userCodeFileName;

    @Value("${sandbox.limit.time:5}")
    private Long timeLimit;

    @Override
    public SandBoxExecuteResult exeJavaCode(Long userId, String userCode, List<String> inputList) {
        //获取容器的Id
        containerId = sandBoxPool.getContainer();
        //创建用户的代码文件
        createUserCodeFile(userCode);
        //编译代码
        CompileResult compileResult = compileCodeByDocker();
        if (!compileResult.isCompiled()) {
            //编译失败,将使用好的容器返回给docker容器池
            sandBoxPool.returnContainer(containerId);
            //删除用户代码的文件
            deleteUserCodeFile();
            //返回编译失败的结果
            return SandBoxExecuteResult.fail(CodeRunStatus.COMPILE_FAILED, compileResult.getExeMessage());
        }
        //编译成功，执行代码
        return executeJavaCodeByDocker(inputList);
    }

    //创建并返回用户代码的文件   创建文件是因为：1.javac命令只能编译物理文件  2.文件系统隔离：每个用户容器有独立目录 3.后续可扩展
    private void createUserCodeFile(String userCode) {
        //获取与特定Docker容器关联的用户代码存储目录路径
        String codeDir = sandBoxPool.getCodeDir(containerId);
        log.info("user-pool路径信息：{}", codeDir);
        //用户代码文件名
        userCodeFileName = codeDir + File.separator + JudgeConstants.USER_CODE_JAVA_CLASS_NAME;
        //如果文件之前存在，将之前的文件删除掉
        if (FileUtil.exist(userCodeFileName)) {
            FileUtil.del(userCodeFileName);
        }
        //将用户代码写入用户代码文件中
        FileUtil.writeString(userCode, userCodeFileName, Constants.UTF8);
    }

    //编译
    //的使用docker编译
    private CompileResult compileCodeByDocker() {
        //创建docker Java 命令的 exec 实例并接收实例返回的Id
        String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVAC_CMD, null, containerId);

        // 准备结果回调处理器
        DockerStartResultCallback resultCallback = new DockerStartResultCallback();//监听执行指令的整个过程
        CompileResult compileResult = new CompileResult();

        try {
            //docker客户端执行命令
            dockerClient.execStartCmd(cmdId)
                    //返回编译成功与否的对象
                    .exec(resultCallback)
                    .awaitCompletion();

            if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                //失败编译
                compileResult.setCompiled(false);
                compileResult.setExeMessage(resultCallback.getErrorMessage());
            } else {
                //成功编译
                compileResult.setCompiled(true);
            }

            //将设置好的回调处理器返回
            return compileResult;
        } catch (InterruptedException e) {
            //此处可以直接抛出 已做统一异常处理  也可再做定制化处理
            throw new RuntimeException(e);
        }
    }

    //运行代码
    private SandBoxExecuteResult executeJavaCodeByDocker(List<String> inputList) {
        List<String> outList = new ArrayList<>(); //记录输出结果
        long maxMemory = 0L;  //最大占用内存
        long maxUseTime = 0L; //最大运行时间

        //执行用户代码
        for (String inputArgs : inputList) {
            //获取待执行的命令
            String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArgs, containerId);

            //执行代码
            StopWatch stopWatch = new StopWatch();
            //执行情况监控
            StatsCmd statsCmd = dockerClient.statsCmd(containerId); //启动监控
            StatisticsCallback statisticsCallback = statsCmd.exec(new StatisticsCallback());//实时获取容器资源统计信息
            stopWatch.start();//执行代码后开始计时
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                //客户端执行代码,将代码的返回响应存储到resultCallback
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        //存在最大运行时间限制
                        .awaitCompletion(timeLimit, TimeUnit.SECONDS);
                if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                    //未通过所有用例返回结果
                    return SandBoxExecuteResult.fail(CodeRunStatus.NOT_ALL_PASSED);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stopWatch.stop();  //结束时间统计
            statsCmd.close();  //结束docker容器执行统计
            long userTime = stopWatch.getLastTaskTimeMillis(); //获取执行耗时
            maxUseTime = Math.max(userTime, maxUseTime);       //记录最大的执行用例耗时
            Long memory = statisticsCallback.getMaxMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, statisticsCallback.getMaxMemory()); //记录最大的执行用例占用内存
            }
            //trim 去除字符串首尾的空白字符  移除以下字符:  不处理字符串中间的空白  并且返回新的对象
            // 空格 ( )
            //制表符 (\t)
            //换行符 (\n 或 \r)
            //其他 Unicode 空白字符（如全角空格 \u3000）
            outList.add(resultCallback.getMessage().trim());   //记录正确的输出结果
        }
        //将使用好的容器返回给客户端
        sandBoxPool.returnContainer(containerId);
        deleteUserCodeFile(); //清理文件

        return getSanBoxResult(inputList, outList, maxMemory, maxUseTime); //封装结果
    }


    //在已运行的 Docker 容器内部创建一个待执行的命令实例
    private String createExecCmd(String[] javaCmdArr, String inputArgs, String containerId) {
        // 1. 处理输入参数
        if (!StrUtil.isEmpty(inputArgs)) {
            //当入参不为空时拼接入参(将入参拼接在指令后面，这样就能够在main函数的String[] args参数中来获取入参)
            String[] inputArray = inputArgs.split(" ");
            //将基础Java命令与用户输入参数组合 在容器中就直接这样运行javaCmdArr: java -cp /app Main file.txt debug
            javaCmdArr = ArrayUtil.append(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArray);
        }

        // 2. 创建Docker exec命令
        ExecCreateCmdResponse cmdResponse = dockerClient.execCreateCmd(containerId)
                // 设置执行的命令 java -cp /app Main   file.txt(入参)  debug(入参)
                .withCmd(javaCmdArr)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();

        return cmdResponse.getId();
    }

    //对返回结果进行封装
    private SandBoxExecuteResult getSanBoxResult(List<String> inputList, List<String> outList,
                                                 long maxMemory, long maxUseTime) {
        if (inputList.size() != outList.size()) {
            //输入用例数量 不等于 输出用例数量  属于执行异常
            return SandBoxExecuteResult.fail(CodeRunStatus.NOT_ALL_PASSED, outList, maxMemory, maxUseTime);
        }
        return SandBoxExecuteResult.success(CodeRunStatus.SUCCEED, outList, maxMemory, maxUseTime);
    }

    //删除用户代码文件
    private void deleteUserCodeFile() {
        FileUtil.del(userCodeFileName);
    }
}